using System;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;

using Gazebo.Core;
using Gazebo.Util;

namespace Gazebo.Core
{
    public class Client : IDisposable
    {
        private TcpClient client;
        
        private Stream netStream;
        
        private TextReader reader;
        
        private TextWriter writer;
        
        private volatile bool running = true;
        
        private Thread thread;
        
        public event EventHandler<ChunkReadEventArgs> ChunkRead;
        
        public event EventHandler ConnectionClosed;
        
        public Client(string hostname, int port, INetworkFilterFactory nfFactory)
        {
            client = new TcpClient(hostname, port);
            
            netStream = client.GetStream();
            if (nfFactory != null) {
                netStream = nfFactory.FilterStream(netStream);
            }
            
            reader = new StreamReader(netStream, Encoding.ASCII);
            writer = new StreamWriter(netStream, Encoding.ASCII);
            
            thread = new Thread(Loop);
        }
        
        public void Start()
        {
            thread.Start();
        }
        
        private void FireConnectionClosed()
        {
            EventHandler h = ConnectionClosed;
            
            if (h == null)
                return;
            
            h(this, EventArgs.Empty);
        }
        
        private void FireChunkRead(string chunk, bool loggedIn)
        {
            EventHandler<ChunkReadEventArgs> h = ChunkRead;
            
            if (h == null)
                return;
            
            h(this, new ChunkReadEventArgs(new Chunk(chunk, loggedIn)));
        }
        
        public void WriteLine(string line)
        {
            writer.WriteLine(line);
            writer.Flush();
        }
        
        private void Loop()
        {
            char[] buffer = new char[8192];
            string chunk;
            
            Chunker chunker = new Chunker();
            chunker.Prompt = "fics% ";
            chunker.ChunkMode = ChunkMode.None;
            
            while (running) {
                if (!client.Client.Poll(500000, SelectMode.SelectRead)) {
                    continue;
                }
                
                if (!running) {
                    break;
                }
                
                int count;
                
                try {
                	count = reader.Read(buffer, 0, buffer.Length);
                } catch (TimeoutException) {
                    continue;
                }
            	
                if (count == 0) {
                    running = false;
                    break;
                }
                
                if (count < 0) {
                    continue;
                }
                
                bool loggedIn = false;
                
                if (chunker.ChunkMode == ChunkMode.None) {
                    if (new string(buffer, 0, count).Contains("fics% ")) {
                        chunker.ChunkMode = ChunkMode.Smart;
                    }
                } else {
                    loggedIn = true;
                }
                
                chunker.Append(buffer, 0, count);
                
                while ((chunk = chunker.NextChunk()) != null) {
                    FireChunkRead(chunk, loggedIn);
                }
            }
            
            chunker.ChunkMode = ChunkMode.None;
            while ((chunk = chunker.NextChunk()) != null) {
                FireChunkRead(chunk, true);
            }
            
            FireConnectionClosed();
        }
        
        public void DisposeSilently()
        {
            ChunkRead = null;
            ConnectionClosed = null;
            
            Dispose();
        }
        
        public void Dispose()
        {
            Dispose(true);
        }
        
        private void Dispose(bool disposing)
        {
            if (disposing) {
                if (thread == null) {
                    return;
                }
                
                running = false;
                
                client.Close();
                netStream.Close();
                writer.Close();
                reader.Close();
                
                thread.Join();
            }
            
            thread = null;
            client = null;
            netStream = null;
            writer = null;
            reader = null;
            
            ChunkRead = null;
            ConnectionClosed = null;
            
            running = false;
        }
        
        ~Client() {
            Dispose(false);
        }
    }
}
